using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using NUnit.Framework.Constraints;

using Rhino.Mocks;

using NPipeline;
using NPipeline.Driver;
using NPipeline.Event;
using System.Threading;

namespace NPipeline.Tests
{
    [TestFixture]
    public class PipelineFixture
    {

        Pipeline p;
        GenericStageDriverFactory<SynchronousStageDriver> factory;

        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {

        }
        [TestFixtureTearDown]
        public void TestFixtureTearDown()
        {

        }
        [SetUp]
        public void SetUp()
        {
            p = new Pipeline();
            factory = new GenericStageDriverFactory<SynchronousStageDriver>();

        }
        [TearDown]
        public void TearDown()
        {

        }
        [Test]
        public void Constructor_Test()
        {
            Assert.IsNotNull(p);
        }

        /// <summary>
        /// Write your test story here.
        /// </summary>
        /// <remarks>
        /// - Desired Result:	Success
        /// - Coordinator:      When
        /// - Conditions:       EmptyPipeline
        /// </remarks>
        [Test]
        public void Success_When_EmptyPipeline()
        {
            Pipeline pEmpty = new Pipeline();

            // verify that we have a SourceFeeder and it is the TerminalFeeder
            // as the pipeline is empty
            Assert.IsInstanceOfType(typeof(TerminalFeeder), pEmpty.getSourceFeeder()); 
            pEmpty.Run();
            // need to add conditions
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void Verify_No_DoubleStart()
        {
            // -- you can double start a empty pipeline ??

            Stage.ConsoleOutStage stage1 = new Stage.ConsoleOutStage("FIRST");
            Stage.AddToCollectionStage<string> stage2 = new NPipeline.Stage.AddToCollectionStage<string>();

            GenericStageDriverFactory<SynchronousStageDriver> factory =
                new GenericStageDriverFactory<SynchronousStageDriver>();

            p.AddStage(stage1, factory);
            p.AddStage(stage2, factory);

            p.Start();
            // should throw an exception
            p.Start();

        }


        [Test]
        public void Verify_Pipeline_Construction_And_Basic_Operation()
        {

            Stage.ConsoleOutStage stage1 = new Stage.ConsoleOutStage("FIRST");
            Stage.AddToCollectionStage<string> stage2 = new NPipeline.Stage.AddToCollectionStage<string>();
            Stage.ConsoleOutStage nullStage = new Stage.ConsoleOutStage("NOT USED !!");


            GenericStageDriverFactory<SynchronousStageDriver> factory =
                new GenericStageDriverFactory<SynchronousStageDriver>();


            // build the pipeline
            p.AddStage(stage1, factory);
            p.AddStage(stage2, factory);

            // verify that we find drivers for each stage
            Assert.IsNotNull(p.getStageDriver(stage1));
            Assert.IsNotNull(p.getStageDriver(stage2));
            // and we should not found the null stage
            Assert.IsNull(p.getStageDriver(nullStage));



            // verify that we have a source feeder 
            Assert.IsInstanceOfType(typeof(IFeeder), p.getSourceFeeder());

            // verify that we have a downstream feeder for each stage
            Assert.IsInstanceOfType(typeof(IFeeder), p.getDownstreamFeeder(stage1));
            Assert.IsInstanceOfType(typeof(IFeeder), p.getDownstreamFeeder(stage2));

            // looks good so far -- lets Run the pipeline

            p.Start();
            p.Finish();

            // nothing was feed into the pipeline, the collection should be empty
            Assert.AreEqual(0, stage2.Collection.Count);


            // since all the drivers are in stopped now - feeding something into it 
            // should not propagate
            p.getSourceFeeder().Feed("STRING");
            Assert.AreEqual(0, stage2.Collection.Count);

            // start the pipeline
            p.Start();

            // since it was a syncrounous driver -- should have been added to the collection
            Assert.AreEqual(1, stage2.Collection.Count);

            // feed another record in the running pipeline and check we got it
            p.getSourceFeeder().Feed("STRING2");
            Assert.AreEqual(2, stage2.Collection.Count);

            // we are done ...
            p.Finish();


        }



        [Test]
        public void Simple_Pipeline()
        {

            Stage.ConsoleOutStage stage = new Stage.ConsoleOutStage("FIRST");
            Stage.ConsoleOutStage stage2 = new Stage.ConsoleOutStage("SECOND");
            Stage.ConsoleOutStage stage3 = new Stage.ConsoleOutStage("THIRD");

            Stage.AddToCollectionStage<string> collStage = 
                new Stage.AddToCollectionStage<string>();

            GenericStageDriverFactory<SynchronousStageDriver> factory = 
                new GenericStageDriverFactory<SynchronousStageDriver>();
              
            p.AddStage(stage, factory);
            p.AddStage(stage2, factory);
            p.AddStage(stage3, factory);
            p.AddStage(collStage, factory);

            p.getSourceFeeder().Feed("FIRST RECORD");
            p.getSourceFeeder().Feed("SECOND RECORD");
            
            // need to add conditions ... and needs some debugging

            p.Start();
            p.getSourceFeeder().Feed("STRING3");
            p.Finish();


            // just make a simple comment change so see if teamcity works ...


            Assert.Contains("FIRST RECORD", collStage.Collection);
            Assert.Contains("SECOND RECORD", collStage.Collection);

        }


        [Test]
        public void Verify_CollectionStage()
        {
            Pipeline p = new Pipeline();

            Stage.ConsoleOutStage stage = new Stage.ConsoleOutStage();

            Stage.AddToCollectionStage<string> collStageString =
                new Stage.AddToCollectionStage<string>();
            Stage.AddToCollectionStage<int> collStageInt =
                new Stage.AddToCollectionStage<int>();


            GenericStageDriverFactory<SynchronousStageDriver> factory =
                new GenericStageDriverFactory<SynchronousStageDriver>();

            p.AddStage(collStageString, factory);
            p.AddStage(stage, factory);
            p.AddStage(collStageInt, factory);

            p.Start();
            p.getSourceFeeder().Feed("FIRST RECORD");
            p.getSourceFeeder().Feed(10);
            p.getSourceFeeder().Feed("SECOND RECORD");
            p.Finish();

            // need to add conditions ... and needs some debugging

            Assert.Contains("FIRST RECORD", collStageString.Collection);
            Assert.AreEqual(2, collStageString.Collection.Count);
            Assert.Contains(10, collStageInt.Collection);
            Assert.AreEqual(1, collStageInt.Collection.Count);

        }

        // need test for the branches 
        // the initialization tests should be done with a test driver exposing the status
        // the also a teststage should be added that does some basic stuff (collecting all items)
        
        // need to test that the pipeline construction is ok also
        // a test feeder also that records the stuff

        [Test]
        public void Branch_Verify()
        {
            // add 2 empty pipelines
            Pipeline branch = new Pipeline();

            p.AddBranch("BRANCH", branch);

            Assert.IsTrue(p.hasBranch(branch));
            Assert.IsFalse(branch.hasBranch(p));
            Assert.AreEqual(p, branch.Parent);

            p.Run();

            var collstage1 = new Stage.AddToCollectionStage<String>();
            var collstage2 = new Stage.AddToCollectionStage<String>();
            var collstage3 = new Stage.AddToCollectionStage<String>();

            p.AddStage(collstage1, factory);
            p.AddStage(collstage2, factory);

            branch.AddStage(collstage3, factory);


            p.Start();
            p.getSourceFeeder().Feed("FIRST");

            Assert.AreEqual(1, collstage1.Collection.Count);
            Assert.AreEqual(1, collstage2.Collection.Count);
            Assert.AreEqual(0, collstage3.Collection.Count);

            p.getBranchFeeder("BRANCH").Feed("SECOND");
            Assert.AreEqual(1, collstage3.Collection.Count);
            p.Finish();









        }


        [Test]
        public void PipelineLifeCycleEvents_Are_Fired()
        {
            bool startWasRaised = false;
            var finishWasRaised = false;
            var notifiers = new List<Pipeline>();

            p.PipelineStart += delegate { startWasRaised = true; };
            p.PipelineFinish += delegate { finishWasRaised = true; };

            // same thing but using C# 3.0 lambda expressions
            // verify that the pipeline marked as sender is the main one
            p.PipelineStart += (sender, e) => { notifiers.Add(e.Source); };

            // add a branch so we see that both pipelines are notifying us
            // buy just subscribing to the main pipeline
            var branch = new Pipeline();
            p.AddBranch("BRANCH", branch);

            p.Run();

            Assert.IsTrue(startWasRaised);
            Assert.IsTrue(finishWasRaised);

            // 2 events raised ??
            Assert.AreEqual(2, notifiers.Count);

            // do we have events from both pipelines ?
            Assert.AreSame(p, notifiers[0]);
            Assert.AreSame(branch, notifiers[1]);


        }


        [Test]
        public void StageEvents_Are_Fired()
        {
            ProcessedObjectEventArgs processedEventWasRaised = null;
            ProcessedObjectEventArgs branchEventWasRaised = null;

            p.StageEvent += (source, e) => { 
                processedEventWasRaised = (ProcessedObjectEventArgs) e; 
            };

            // Create a Mock Stage that will Fire an Event on Process called

            var mocks = new MockRepository();
            var mockStage = mocks.StrictMock<IStage>();

            // that is the sequence of events we expect to be called
            Expect.Call(() => mockStage.Init(p));
            Expect.Call(mockStage.PreProcess);
            Expect.Call(mockStage.PostProcess);
            Expect.Call(mockStage.Release);

            // just make sure that we raise the Event if the process method is called
            // this is stubbing a mockStage handling ...
            Expect.Call(() => mockStage.Process(null))
                        .IgnoreArguments()
                        .Callback((object obj) => { 
                            p.RaiseStageEvent(new ProcessedObjectEventArgs(mockStage,obj));
                            return true;
                        });

            // done setup the expectations
            mocks.ReplayAll();


            p.AddStage(mockStage, factory);

            // add a branch so we see that both pipelines are notifying us
            // buy just subscribing to the main pipeline
            var branch = new Pipeline();
            p.AddBranch("BRANCH", branch);

            // check that the event is raised on a different Thread
            branch.AsyncEvents = true;

            int mainThread = Thread.CurrentThread.ManagedThreadId;
            int eventThread = -1;


            // add a event on the branch
            branch.StageEvent += (sender, e) => { 
                branchEventWasRaised = (ProcessedObjectEventArgs) e;
                // make sure the branch event is raised by a different thread
                eventThread = Thread.CurrentThread.ManagedThreadId;
            };

            // for an event to be fired - we need to feed a object into it

            p.getSourceFeeder().Feed("Hello");

            p.Run();

            // we need to wait for the async events !! the unit test might fail
            // because the event was not processed yet ...
            // make sure that if you raise the events asyncrounous that we finished
            // processing !!!
            Thread.Sleep(1000);

            Assert.IsNotNull(processedEventWasRaised);
            Assert.AreSame(mockStage, processedEventWasRaised.Source);
            Assert.IsNotNull(branchEventWasRaised);
            Assert.AreSame(mockStage, branchEventWasRaised.Source);

            // event was raised on a different thread ?
            Assert.AreNotEqual(eventThread, mainThread);

        }


        [Test]
        public void StageErrorEvents_Are_Fired()
        {
            StageErrorEventArgs stageErrorEvent = null;

            p.StageErrorEvent += (source, e) =>
            {
                stageErrorEvent = (StageErrorEventArgs)e;
            };

            // Create a Mock Stage that will 
            // raise an StageException if Process is called

            var mocks = new MockRepository();
            IStage mockStage = mocks.StrictMock<IStage>();

            // that is the sequence of events we expect to be called
            Expect.Call(delegate { mockStage.Init(p); });
            Expect.Call(delegate { mockStage.PreProcess(); });
            Expect.Call(delegate { mockStage.PostProcess(); });
            Expect.Call(delegate { mockStage.Release(); });

            // just make sure that we raise the Event if the process method is called
            // this is stubbing a mockStage handling ...
            Expect.Call(() => { mockStage.Process(null); })
                        .IgnoreArguments()
                        .Throw( new StageException(mockStage, "EXCEPTION FROM MOCK") );

            // done setup the expectations
            mocks.ReplayAll();


            p.AddStage(mockStage, factory);

            // for an event to be fired - we need to feed a object into it
            p.getSourceFeeder().Feed("Hello");

            p.Run();

            Assert.IsNotNull(stageErrorEvent);
            Assert.AreSame(mockStage, stageErrorEvent.Source);
            Assert.AreEqual(StageState.PROCESS, stageErrorEvent.State);
            Assert.AreEqual("Hello", stageErrorEvent.Data);

            // verify that the processing exception was also recorded in the
            // stage driver
            Assert.AreEqual(1, p.getStageDriver(mockStage).ProcessingExceptions.Count);

        }


        [Test]
        public void TestStages_with_Mocks()
        {

            // verify the Pipeline IStage interaction ...
            // buy just using Mocked Stages

            var mocks = new MockRepository();
            IStage mockStage = mocks.StrictMock<IStage>();

            String[] content = { "PIPE1", "PIPE2" };
            Expect.Call(delegate { mockStage.Init(p); });
            Expect.Call(delegate { mockStage.PreProcess(); });
            Expect.Call(delegate { mockStage.Process(content[0]); });
            Expect.Call(delegate { mockStage.Process(content[1]); });
            Expect.Call(delegate { mockStage.PostProcess(); });
            Expect.Call(delegate { mockStage.Release(); });

            mocks.ReplayAll();

            p.AddStage(mockStage, factory);



            foreach (String c in content) p.getSourceFeeder().Feed(c);
            p.Run();

            mocks.VerifyAll();


        }

        [Test]
        public void TestPooledObjectStage()
        {

            var stage = new Stage.PoolObjectsStage<string>(2);
            var collStage = new Stage.AddToCollectionStage<List<string>>();

            p.AddStage(stage,factory);
            p.AddStage(collStage, factory);


            p.getSourceFeeder().Feed("FIRST");
            p.getSourceFeeder().Feed("SECOND");
            p.getSourceFeeder().Feed("THIRD");

            p.Run();

            Console.WriteLine(collStage.Collection[0][0]);

            Assert.AreEqual("FIRST",collStage.Collection[0][0]);
            Assert.AreEqual("SECOND", collStage.Collection[0][1]);
            Assert.AreEqual("THIRD", collStage.Collection[1][0]);









        }




    }
}
